package com.flex.showdoc4j.spring;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import static org.springframework.util.StringUtils.tokenizeToStringArray;

/**
 * @author ganlt
 * @since 2019/08/01 8:37
 */
@Slf4j
@Component
public class DocRegisterBean implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {
    private boolean serviceMock = false;
    private ApplicationContext applicationContext;
    ClassLoader defaultClassLoader;
    ClassLoader systemClassLoader;
    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

    {
        try {
            systemClassLoader = ClassLoader.getSystemClassLoader();
        } catch (SecurityException ignored) {
            // AccessControlException on Google App Engine
        }

        Properties properties = new Properties();
        try {
            InputStream in = METADATA_READER_FACTORY.getClass().getClassLoader().getResourceAsStream("doc.properties");
            properties.load(in);
            serviceMock = Boolean.valueOf(properties.getProperty("serviceMock"));
        } catch (Exception e) {

        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if (serviceMock && SpringTools.isDev(applicationContext)) {
            try {
                scanClasses("cn.tcampus.**.service", null).stream()
                        .filter(Class::isInterface)
                        .forEach(clazz -> {
                            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                            GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) builder.getRawBeanDefinition();
                            genericBeanDefinition.getPropertyValues().add("interfaceClass", genericBeanDefinition.getBeanClassName());
                            genericBeanDefinition.setBeanClass(DocSpringFactoryBean.class);
                            genericBeanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);

                            registry.registerBeanDefinition(toLowerCaseFirstOne(clazz.getSimpleName()), genericBeanDefinition);
                        });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return Character.toLowerCase(s.charAt(0)) + s.substring(1);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType)
            throws IOException {
        Set<Class<?>> classes = new HashSet<>();
        String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packagePattern : packagePatternArray) {
            Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
            for (Resource resource : resources) {
                try {
                    ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = classForName(classMetadata.getClassName());
                    if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
                        classes.add(clazz);
                    }
                } catch (Throwable e) {
                    log.warn("查询的资源不存在");
                }
            }
        }
        return classes;
    }

    public Class<?> classForName(String name) throws ClassNotFoundException {
        return classForName(name, getClassLoaders(null));
    }

    Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {
        for (ClassLoader cl : classLoader) {
            if (null != cl) {
                try {
                    Class<?> c = Class.forName(name, true, cl);

                    if (null != c) {
                        return c;
                    }
                } catch (ClassNotFoundException e) {
                }
            }
        }
        throw new ClassNotFoundException("找不到class对象: " + name);
    }

    private ClassLoader[] getClassLoaders(ClassLoader classLoader) {
        return new ClassLoader[]{
                classLoader,
                defaultClassLoader,
                Thread.currentThread().getContextClassLoader(),
                getClass().getClassLoader(),
                systemClassLoader};
    }
}
